#include<iostream>
#include<pthread.h>
#include<queue>
using namespace std;
//阻塞队列:进行操作的时候可能会被阻塞   若队列为空,想要获取数据就会被阻塞 若队列为满,想要放入数据就会被阻塞
//实现:条件变量+互斥锁
//成员：
//1.使用STL当中的queue实现阻塞队列,阻塞队列是被多执行流同时访问的临界资源由于STL当中的容器是不保证线程安全的，所以需要加锁保护阻塞队列
//2.容量:表示阻塞队列最多可以存放多少个元素,一旦满了,那么生产者就不能再生产,知道消费者进行消费,阻塞队列当中由空间了
//3.两个条件变量 empty:当阻塞队列为空的时候,消费者要在empty条件变量下进行等待  full:当阻塞队列为满的时候,生产者要在full条件变量下等待
//4.互斥锁：保护阻塞队列

#define CAP_MAX 5 //阻塞队列最多可以存储的元素个数
template<class T> //存储的数据模板化,方便进行复用
class BlockQueue
{
public:
    BlockQueue(int cap = CAP_MAX) :_cap(cap)
    {
      pthread_mutex_init(&_mtx,NULL);      
      pthread_cond_init(&_empty,NULL);
      pthread_cond_init(&_full,NULL);
    }
    ~BlockQueue()
    {
      pthread_mutex_destroy(&_mtx);
      pthread_cond_destroy(&_empty);
      pthread_cond_destroy(&_full);
    }
    void push(const T& val) //生产者才会调用该函数放数据
    {
        //想要操作阻塞队列(临界资源),必须先申请锁
        pthread_mutex_lock(&_mtx);
        //判断是否符合生产条件
        //判断条件要使用while而不是使用if
        //原因1:等待是可能失败的,如果用if,等待失败了,执行流会继续往下执行,造成错误
        //原因2:可能会存在伪唤醒,假设生产者生产了一个数据之后,唤醒所有正在等待的消费者
        //由于只有一个线程可以消费该数据,其余消费者就会被伪唤醒，消费者往下执行,造成错误
        //正确写法:使用while,当线程被唤醒之后,再次判断是否真的满足生产消费条件
        while(isFull())
        {
          pthread_cond_wait(&_full,&_mtx);//在_full条件变量下等待,会释放当前对应的互斥锁,被唤醒之后,竞争锁成功了才能返回
        }
        //被唤醒&竞争锁成功返回 
        _bq.push(val);
        //先解锁还是先唤醒都可以
        //先解锁再唤醒:锁已经释放了,此时消费者线程就可以立马竞争锁,竞争锁成功之后,pthread_cond_wait函数才能返回
        //先唤醒再解锁:此时锁可能释放/没释放,但是不影响，因为比竞争锁成功,获得锁才能返回
        pthread_mutex_unlock(&_mtx);
        //唤醒在empty条件变量下等待的消费者进行消费
        pthread_cond_signal(&_empty);

        //形式2:当阻塞队列当中存储的数据大于队列容量的一半时,再唤醒消费者线程进行消费
        //if(_bq.size()>_cap/2) pthread_cond_signal(&_empty); //唤醒在empty条件变量下等待的消费者线程
  
    }
    void pop(T* data) //输出型参数,消费者获取数据
    {
        pthread_mutex_lock(&_mtx);
        while(isEmpty())
        {
          pthread_cond_wait(&_empty,&_mtx);
        }
        *data = _bq.front();
        _bq.pop();
        pthread_mutex_unlock(&_mtx);
        pthread_cond_signal(&_full);//唤醒在full条件变量下等待的生产者进行生产
        
        //形式2:当阻塞队列当中存储的数据小于队列容器的一半时,再唤醒生产者线程进行生产
       //if(_bq.size()< _cap/2)  pthread_cond_signal(&_full); //唤醒在full条件变量下等待的生产者线程
    }
    bool isEmpty()
    {
        return _bq.size() == 0;
    }
    bool isFull()
    {
        return _cap == _bq.size();
    }
private:
    queue<T> _bq;
    pthread_mutex_t _mtx;
    pthread_cond_t _empty;
    pthread_cond_t _full;
    int _cap;
};
